﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Device.Location;
using System.Windows;
namespace Phone7ActionPack.Controls
{
	public class LocationControl : FrameworkElement, IDisposable,INotifyPropertyChanged
	{
		#region Location History
		
		private ObservableCollection<GeoPosition<GeoCoordinate>> history = new ObservableCollection<GeoPosition<GeoCoordinate>>();
		private ReadOnlyObservableCollection<GeoPosition<GeoCoordinate>> readonlyHistory;

		/// <summary>
		/// this is the collection that the public property History is based on that is accessable 
		/// for derived types 
		/// </summary>
		protected ObservableCollection<GeoPosition<GeoCoordinate>> LocationHistory
		{
			get { return this.history; }
		}

		/// <summary>
		/// collection of all the recorded points in the control has recorded
		/// </summary>
		public ReadOnlyObservableCollection<GeoPosition<GeoCoordinate>> History
		{
			get
			{
				if (readonlyHistory == null)
					readonlyHistory = new ReadOnlyObservableCollection<GeoPosition<GeoCoordinate>>(history);
				return readonlyHistory;
			}
		}

		#endregion

		protected const string VISUAL_STATE_FORMAT = "{0}State";
		private GeoCoordinateWatcher watcher;
		#region GeoCoordinateWatcher Direct Interaction

		/// <summary>
		/// Method that directly enables the GeoCoordinateWatcher
		/// </summary>
		protected void StartWatcher()
		{

			// if permission is denyed then ask them for it. It could just be denied base on the devices location service being turned off as per MSDN http://msdn.microsoft.com/en-us/library/system.device.location.geocoordinatewatcher.permission(VS.92).aspx

			if (this.watcher.Permission == GeoPositionPermission.Granted) // if permission is granted don't ask them for permission
				this.watcher.Start(true);
			else
				this.watcher.Start(false);
			this.watcherStarted = true;
		}

		/// <summary>
		/// Method that directly disables the GeoCoordinateWatcher
		/// </summary>
		protected void StopWatcher()
		{
			this.watcher.Stop();
			this.watcherStarted = false;
		}


		#region Watcher Event Handlers
		private void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
		{
			this.LocationStatus = e.Status;
			
		}

		private void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
		{
			
			this.Speed = e.Position.Location.Speed;
			this.Latitude = e.Position.Location.Latitude;
			this.Longitude = e.Position.Location.Longitude;
			this.VerticalAccuracy = e.Position.Location.VerticalAccuracy;
			this.HorizontalAccuracy = e.Position.Location.HorizontalAccuracy;
			this.IsUnknownLocation = e.Position.Location.IsUnknown;
			this.CurrentLocationTimestamp = e.Position.Timestamp;
			this.Course = e.Position.Location.Course;
			this.history.Add(e.Position);
			//this.GeoCoordinate = e.Position.Location;
			if (this.LocationChanged != null)
				this.LocationChanged(this, EventArgs.Empty);
			
		}

		void watcher_PermissionChanged(object sender, GeoPermissionChangedEventArgs e)
		{

		}

		#endregion

		#region Extensibility Watcher Event Callbacks
		/// <summary>
		/// This method will be called so that classes that extend the location Control 
		/// </summary>
		/// <param name="e"></param>
		protected virtual void OnWatcherPositionChanged(GeoPositionChangedEventArgs<GeoCoordinate> e) { }
		protected virtual void OnWatcherPermissionChanged(GeoPermissionChangedEventArgs e) { }
		protected virtual void OnWatcherPositionStatusChanged(GeoPositionStatusChangedEventArgs e) { }

		#endregion


		#endregion

		private bool watcherStarted;

		private bool useEmulation;
		public bool UseEmulation
		{
			get { return this.useEmulation; }
			set
			{
				if (this.useEmulation != value)
				{
					this.useEmulation = value;
					if (value)
						kickOffEmulator();
					this.OnPropertyChanged("UseEmulation");
				}
			}
		}

		private void kickOffEmulator()
		{
			Emulation.LocationWalker.LocationWalkerChanged += new Emulation.LocationWalkerChangedHandler(LocationWalker_LocationWalkerChanged);
			
		}

		void LocationWalker_LocationWalkerChanged(object sender, Emulation.LocationWalkerChangedEventArgs e)
		{
			
			this.Latitude = e.Latitude;
			
			this.Longitude = e.Longitude;
			this.Speed = e.Speed;
			this.Course = e.Course;
			this.history.Add(new GeoPosition<GeoCoordinate>(new DateTimeOffset(e.ReadingDateTime), e.GeoCoordinate));
			this.GeoCoordinate = e.GeoCoordinate;
			if (this.LocationChanged != null)
				this.LocationChanged(this, EventArgs.Empty);
			this.OnPropertyChanged("History");
			}


		#region Events

		public event EventHandler LocationWatcherStarted;
		public event EventHandler LocationWatcherStopped;
		public event EventHandler LocationChanged;
		#endregion
		
		#region Dependency Properties

		#region Device Location
		#region LocationStatus

		public static readonly DependencyProperty LocationStatusProperty =
			DependencyProperty.Register("LocationStatus", typeof(GeoPositionStatus), typeof(LocationControl), new PropertyMetadata(LocationStatusProperty_ChangedCallback));

		[Description("The device's Location Service Status")]
		[ReadOnly(true)]
		
		[EditorBrowsable(EditorBrowsableState.Always)]
		[Category("Device Location")]
		public GeoPositionStatus LocationStatus
		{
			get { return (GeoPositionStatus)GetValue(LocationStatusProperty); }
			protected set { SetValue(LocationStatusProperty, value); }
		}

		/// <summary>
		/// DP changed call back for LocationStatus
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its LocationStatus property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void LocationStatusProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;
			element.OnLocationStatusChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle LocationStatus changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnLocationStatusChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region Speed

		public static readonly DependencyProperty SpeedProperty =
			DependencyProperty.Register("Speed", typeof(double), typeof(LocationControl), new PropertyMetadata(SpeedProperty_ChangedCallback));

		[Description("The device's speed that it is reporting. I am guessing that it is in meters/seconds")]
		[ReadOnly(true)]
		[EditorBrowsable(EditorBrowsableState.Always)]
		[Category("Device Location")]
		public double Speed
		{
			get { return (double)GetValue(SpeedProperty); }
			protected set { SetValue(SpeedProperty, value); }
		}

		/// <summary>
		/// DP changed call back for Speed
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its Speed property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void SpeedProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnSpeedChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle Speed changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnSpeedChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region Latitude

		public static readonly DependencyProperty LatitudeProperty =
			DependencyProperty.Register("Latitude", typeof(double), typeof(LocationControl), new PropertyMetadata(LatitudeProperty_ChangedCallback));

		[Description("The device's Latitude that it is reporting.")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public double Latitude
		{
			get { return (double)GetValue(LatitudeProperty); }
			protected set { SetValue(LatitudeProperty, value); }
		}

		/// <summary>
		/// DP changed call back for Latitude
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its Latitude property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void LatitudeProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnLatitudeChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle Latitude changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnLatitudeChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region Longitude

		public static readonly DependencyProperty LongitudeProperty =
			DependencyProperty.Register("Longitude", typeof(double), typeof(LocationControl), new PropertyMetadata(LongitudeProperty_ChangedCallback));

		[Description("The device's Longitude that it is reporting.")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public double Longitude
		{
			get { return (double)GetValue(LongitudeProperty); }
			protected set { SetValue(LongitudeProperty, value); }
		}

		/// <summary>
		/// DP changed call back for Longitude
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its Longitude property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void LongitudeProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnLongitudeChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle Longitude changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnLongitudeChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region HorizontalAccuracy

		public static readonly DependencyProperty HorizontalAccuracyProperty =
			DependencyProperty.Register("HorizontalAccuracy", typeof(double), typeof(LocationControl), new PropertyMetadata(HorizontalAccuracyProperty_ChangedCallback));

		[Description("The device's margin of error for the Latitude that it is reporting in meters.")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public double HorizontalAccuracy
		{
			get { return (double)GetValue(HorizontalAccuracyProperty); }
			protected set { SetValue(HorizontalAccuracyProperty, value); }
		}

		/// <summary>
		/// DP changed call back for HorizontalAccuracy
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its HorizontalAccuracy property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void HorizontalAccuracyProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnHorizontalAccuracyChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle HorizontalAccuracy changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnHorizontalAccuracyChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region VerticalAccuracy

		public static readonly DependencyProperty VerticalAccuracyProperty =
			DependencyProperty.Register("VerticalAccuracy", typeof(double), typeof(LocationControl), new PropertyMetadata(VerticalAccuracyProperty_ChangedCallback));

		[Description("The device's margin of error for the Latitude that it is reporting in meters.")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public double VerticalAccuracy
		{
			get { return (double)GetValue(VerticalAccuracyProperty); }
			protected set { SetValue(VerticalAccuracyProperty, value); }
		}

		/// <summary>
		/// DP changed call back for VerticalAccuracy
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its VerticalAccuracy property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void VerticalAccuracyProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnVerticalAccuracyChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle VerticalAccuracy changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnVerticalAccuracyChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region IsUnknownLocation

		public static readonly DependencyProperty IsUnknownLocationProperty =
			DependencyProperty.Register("IsUnknownLocation", typeof(bool), typeof(LocationControl), new PropertyMetadata(IsUnknownLocationProperty_ChangedCallback));

		[Description("Does the device know where it is? True = no, False = yes. Common sense right?")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public bool IsUnknownLocation
		{
			get { return (bool)GetValue(IsUnknownLocationProperty); }
			protected set { SetValue(IsUnknownLocationProperty, value); }
		}

		/// <summary>
		/// DP changed call back for IsUnknownLocation
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its IsUnknownLocation property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void IsUnknownLocationProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnIsUnknownLocationChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle IsUnknownLocation changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnIsUnknownLocationChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region GeoCoordinate

		private GeoCoordinate geoCoordinate;
		public GeoCoordinate GeoCoordinate
		{
			get { return this.geoCoordinate; }
			set
			{
				//if (this.geoCoordinate != value)
				//{
					this.geoCoordinate = value;
					this.OnPropertyChanged("GeoCoordinate");
					this.OnGeoCoordinateChanged();
				//}
				
			}
		}

		//public static readonly DependencyProperty GeoCoordinateProperty =
		//    DependencyProperty.Register("GeoCoordinate", typeof(GeoCoordinate), typeof(LocationControl), new PropertyMetadata(GeoCoordinateProperty_ChangedCallback));

		//[Description("GeoCoordinate is the Windows Phone 7's default class to identify a location")]
		//[Category("Device Location")]
		//public GeoCoordinate GeoCoordinate
		//{
		//    get { return (GeoCoordinate)GetValue(GeoCoordinateProperty); }
		//    protected set { SetValue(GeoCoordinateProperty, value); }
		//}

		///// <summary>
		///// DP changed call back for GeoCoordinate
		///// </summary>
		///// <param name="obj">instance of LocationControl that is having its GeoCoordinate property changed</param>
		///// <param name="e">the DP changed arguments</param>
		//private static void GeoCoordinateProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		//{
		//    LocationControl element = obj as LocationControl;

		//    element.OnGeoCoordinateChanged(e);
		//}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle GeoCoordinate changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnGeoCoordinateChanged()
		{

		}

		#endregion

		#region CurrentLocationTimestamp

		public static readonly DependencyProperty CurrentLocationTimestampProperty =
			DependencyProperty.Register("CurrentLocationTimestamp", typeof(DateTimeOffset), typeof(LocationControl), new PropertyMetadata(CurrentLocationTimestampProperty_ChangedCallback));

		[Description("This is the timestamp of the last reading that the device took")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public DateTimeOffset CurrentLocationTimestamp
		{
			get { return (DateTimeOffset)GetValue(CurrentLocationTimestampProperty); }
			protected set { SetValue(CurrentLocationTimestampProperty, value); }
		}

		/// <summary>
		/// DP changed call back for CurrentLocationTimestamp
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its CurrentLocationTimestamp property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void CurrentLocationTimestampProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnCurrentLocationTimestampChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle CurrentLocationTimestamp changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnCurrentLocationTimestampChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion

		#region Course

		public static readonly DependencyProperty CourseProperty =
			DependencyProperty.Register("Course", typeof(double), typeof(LocationControl), new PropertyMetadata(CourseProperty_ChangedCallback));
		[Description("The Course that the device is heading in 0=North, 90=East, 180=South, 270=West")]
		[ReadOnly(true)]
		[Category("Device Location")]
		public double Course
		{
			get { return (double)GetValue(CourseProperty); }
			protected set { SetValue(CourseProperty, value); }
		}

		/// <summary>
		/// DP changed call back for Course
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its Course property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void CourseProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;

			element.OnCourseChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle Course changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnCourseChanged(DependencyPropertyChangedEventArgs e)
		{

		}

		#endregion
		#endregion

		#region Device Location Control

		#region GeoPositionPermission 

		public static readonly DependencyProperty GeoPositionPermissionProperty =
			DependencyProperty.Register("GeoPositionPermission", typeof(GeoPositionPermission), typeof(LocationControl), new PropertyMetadata(GeoPositionPermissionProperty_ChangedCallback));

		public GeoPositionPermission GeoPositionPermission
		{
			get { return (GeoPositionPermission)GetValue(GeoPositionPermissionProperty); }
			protected set { SetValue(GeoPositionPermissionProperty, value); }
		}

		/// <summary>
		/// DP changed call back for GeoPositionPermission
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its GeoPositionPermission property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void GeoPositionPermissionProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;
			
			element.OnGeoPositionPermissionChanged(e);
		}
		
		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle GeoPositionPermission changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnGeoPositionPermissionChanged(DependencyPropertyChangedEventArgs e)
		{
			
		}

		#endregion

		#region LocationWatcherEnabled 

		public static readonly DependencyProperty LocationWatcherEnabledProperty =
			DependencyProperty.Register("LocationWatcherEnabled", typeof(bool), typeof(LocationControl), new PropertyMetadata(LocationWatcherEnabledProperty_ChangedCallback));

		[Description("Is the Location Watcher Enabled")]
		[EditorBrowsable(EditorBrowsableState.Always)]
		[Category("Device Location Control")]
		public bool LocationWatcherEnabled
		{
			get { return (bool)GetValue(LocationWatcherEnabledProperty); }
			set { SetValue(LocationWatcherEnabledProperty, value); }
		}

		/// <summary>
		/// DP changed call back for LocationWatcherEnabled
		/// </summary>
		/// <param name="obj">instance of LocationControl that is having its LocationWatcherEnabled property changed</param>
		/// <param name="e">the DP changed arguments</param>
		private static void LocationWatcherEnabledProperty_ChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
		{
			LocationControl element = obj as LocationControl;
			if (element.watcherStarted)
				if (element.UseEmulation)
					Emulation.LocationWalker.Enable();
				else
					element.StopWatcher();
			else
				if (element.UseEmulation)
					Emulation.LocationWalker.Disable();
				else
					element.StartWatcher();

			element.OnLocationWatcherEnabledChanged(e);
		}

		/// <summary>
		/// Method will enable classes that derive from LocationControl to handle LocationWatcherEnabled changes
		/// </summary>
		/// <param name="e">the DP changed arguments passed in to the DP changed callback</param>
		protected virtual void OnLocationWatcherEnabledChanged(DependencyPropertyChangedEventArgs e)
		{
			
		}

		#endregion

		#endregion

		#endregion

		public LocationControl()
		{
			watcher = new System.Device.Location.GeoCoordinateWatcher();
			if (System.ComponentModel.DesignerProperties.IsInDesignTool)
				this.InitDesignTime();
			else
				this.InitRunTime();
		}

		#region Contorl Init
		protected virtual void InitDesignTime()
		{
			
			var location = new System.Device.Location.GeoCoordinate(23, 43, 100, 10, 10, 5, 123);
			var dummyLocation = new GeoPosition<GeoCoordinate>(new DateTimeOffset(), location);
			this.history.Add(dummyLocation);
		}
		protected virtual void InitRunTime()
		{

			
			watcher.PermissionChanged += new EventHandler<System.Device.Location.GeoPermissionChangedEventArgs>(watcher_PermissionChanged);
			watcher.PositionChanged += new EventHandler<System.Device.Location.GeoPositionChangedEventArgs<System.Device.Location.GeoCoordinate>>(watcher_PositionChanged);
			watcher.StatusChanged += new EventHandler<System.Device.Location.GeoPositionStatusChangedEventArgs>(watcher_StatusChanged);

		}

		#endregion

		public void Dispose()
		{
			this.watcher.Stop();
			this.watcher.Dispose();
			if (UseEmulation)
			{
				Emulation.LocationWalker.LocationWalkerChanged -= new Emulation.LocationWalkerChangedHandler(LocationWalker_LocationWalkerChanged);
				Emulation.LocationWalker.Disable();
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;
		protected void OnPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
	}
}